#include <time.h>
#include <QtCore/QMetaObject>
#include <QtCore/QTimerEvent>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#include <QyDebug.h>
#include "QyNet.h"

using namespace QyNet;

/**************************************************************************************************/

const quint32 SpeedTestPackegeType = 7415963;

// static
void Kernel::registerFactory( AbstractDatagramEncoderFactory * datagramEncoderFactory ) {

    if (!datagramEncoderFactory) {

        qWarning("QyNet::Kernel: Can't register \"0\" as datagram encoder factory.");
        return;
    }

    quint32 type = datagramEncoderFactory->type();

    if ( instance()->p_factories.contains(type) )
        qWarning( "QyNet::Kernel: Replace datagram encoder factory for \"%d\" type.", type );

    instance()->p_factories[type] = datagramEncoderFactory;

}

// static
AbstractDatagramEncoderFactory * Kernel::unregisterFactory( quint32 type ) {

    return instance()->p_factories.contains(type) ? instance()->p_factories.take(type) : 0;
}

// static
Kernel * Kernel::instance() {

    static Kernel kernel;

    return &kernel;
}

AbstractDatagramEncoder * Kernel::createEncoder( quint32 type ) const {

    return p_factories.contains(type) ? p_factories[type]->create() : 0;
}

/**************************************************************************************************/

ConnectionRecord::ConnectionRecord( QTcpSocket * socket, QObject * parent )
    : QObject(parent), p_socket(socket)
{

    currentSize = 0;
    p_socket->setParent(this);
    p_socket->setObjectName("socket");
    QMetaObject::connectSlotsByName(this);
    connect( p_socket, SIGNAL( disconnected() ), SIGNAL( disconnected() ) );

}

void ConnectionRecord::send( AbstractDatagramEncoder * datagram ) {

    QByteArray data;
    QDataStream ds( &data, QIODevice::WriteOnly );
    ds.setVersion(QDataStream::Qt_4_0);

    ds << qint64(0);
    ds << datagram->type();
    ds << datagram->encode();
    ds.device()->seek(0);
    ds << qint64( data.size() - sizeof(qint64) );

    p_socket->write(data);

}

void ConnectionRecord::sendSpeedTest() {

    sendSpeedTest( 1, clock() );

}

void ConnectionRecord::on_socket_error( QAbstractSocket::SocketError socketError ) {

    switch (socketError) {

        case QAbstractSocket::ConnectionRefusedError: qWarning("Socket error: The connection was refused by the peer (or timed out)." ); break;
//         case QAbstractSocket::RemoteHostClosedError: qWarning("Socket error: The remote host closed the connection. Note that the client socket (i.e., this socket) will be closed after the remote close notification has been sent." ); break;
        case QAbstractSocket::RemoteHostClosedError: return;
        case QAbstractSocket::HostNotFoundError: qWarning("Socket error: The host address was not found." ); break;
        case QAbstractSocket::SocketAccessError: qWarning("Socket error: The socket operation failed because the application lacked the required privileges." ); break;
        case QAbstractSocket::SocketResourceError: qWarning("Socket error: The local system ran out of resources (e.g., too many sockets)." ); break;
        case QAbstractSocket::SocketTimeoutError: qWarning("Socket error: The socket operation timed out." ); break;
        case QAbstractSocket::DatagramTooLargeError: qWarning("Socket error: The datagram was larger than the operating system's limit (which can be as low as 8192 bytes)." ); break;
        case QAbstractSocket::NetworkError: qWarning("Socket error: An error occurred with the network (e.g., the network cable was accidentally plugged out)." ); break;
        case QAbstractSocket::AddressInUseError: qWarning("Socket error: The address specified to QUdpSocket::bind() is already in use and was set to be exclusive." ); break;
        case QAbstractSocket::SocketAddressNotAvailableError: qWarning("Socket error: The address specified to QUdpSocket::bind() does not belong to the host." ); break;
        case QAbstractSocket::UnsupportedSocketOperationError: qWarning("Socket error: The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support)." ); break;
        case QAbstractSocket::ProxyAuthenticationRequiredError: qWarning("Socket error: The socket is using a proxy, and the proxy requires authentication." ); break;
        case QAbstractSocket::SslHandshakeFailedError: qWarning("Socket error: The SSL/TLS handshake failed, so the connection was closed (only used in QSslSocket)" ); break;
        case QAbstractSocket::UnfinishedSocketOperationError: qWarning("Socket error: Used by QAbstractSocketEngine only, The last operation attempted has not finished yet (still in progress in the background)." ); break;
#if ( QT_VERSION >= 0x040500 )
        case QAbstractSocket::ProxyConnectionRefusedError: qWarning("Socket error: Could not contact the proxy server because the connection to that server was denied" ); break;
        case QAbstractSocket::ProxyConnectionClosedError: qWarning("Socket error: The connection to the proxy server was closed unexpectedly (before the connection to the final peer was established)" ); break;
        case QAbstractSocket::ProxyConnectionTimeoutError: qWarning("Socket error: The connection to the proxy server timed out or the proxy server stopped responding in the authentication phase." ); break;
        case QAbstractSocket::ProxyNotFoundError: qWarning("Socket error: The proxy address set with setProxy() (or the application proxy) was not found." ); break;
        case QAbstractSocket::ProxyProtocolError: qWarning("Socket error: The connection negotiation with the proxy server because the response from the proxy server could not be understood." ); break;
#endif
        case QAbstractSocket::UnknownSocketError: qWarning("Socket error: An unidentified error occurred." ); break;

    }

    qWarning( "    Error string: \"%s\"", qPrintable( socket()->errorString() ) );
    emit errorMessage();

}

void ConnectionRecord::on_socket_readyRead() {

    QDataStream ds(p_socket);

    ds.setVersion(QDataStream::Qt_4_0);
    qint64 bytesAvailable;
    quint32 type;

//     QyDbgLocation();

    forever {

        bytesAvailable = p_socket->bytesAvailable();

//         QyDbgValue(bytesAvailable);

        if ( currentSize == 0 ) {

            if ( bytesAvailable < qint64( sizeof(qint64) ) )
                break;

            ds >> currentSize;

        }

//         QyDbgValue(currentSize);

        if ( bytesAvailable < currentSize )
            break;

        ds >> type;

        if ( type == SpeedTestPackegeType ) {

            quint32 time;
            quint8 flag;
            QByteArray data;

            ds >> time;
            ds >> flag;
            ds >> data;

            if (flag) {

                sendSpeedTest( 0, time );

            } else {

                quint32 elapsed = clock() - time;

                if (!elapsed)
                    elapsed = 1;

                qreal secs = elapsed / 1000.0;

                emit speedTestComplete( data.size() / secs );
            }

        } else {

            AbstractDatagramEncoder * decoder = Kernel::instance()->createEncoder(type);

            if (decoder) {

                QByteArray data;
                ds >> data;
                decoder->decode(data);
//                 qDebug("emit datagramReceived(decoder);");
                emit datagramReceived(decoder);
                delete decoder;

            } else {

                p_socket->seek(currentSize);
                qWarning( "QyNet::ConnectionRecord: Skip unknow package %d, size: %d.", type, quint32(currentSize) );

            }

        }

        currentSize = 0;

    }

}

void ConnectionRecord::sendSpeedTest( quint8 flag, quint32 time ) {

    QByteArray data;
    QDataStream ds( &data, QIODevice::WriteOnly );
    ds.setVersion(QDataStream::Qt_4_0);

    ds << qint64(0);
    ds << SpeedTestPackegeType;
    ds << time;
    ds << flag;
    ds << QByteArray( 1024 * 1024, 'A' );
    ds.device()->seek(0);
    ds << qint64( data.size() - sizeof(qint64) );

    p_socket->write(data);

}

/**************************************************************************************************/
#define QyNetDoProtected(protector,action,timeout) \
    if ( protector.tryLock(timeout) ) { action; protector.unlock(); }
const quint32 defaultTimeout = 1000;

bool Server::start( quint16 port, const QHostAddress & address ) {

    return sv_socket->listen( address, port );
}

QList<ConnectionRecord*> Server::connections() {

    QList<ConnectionRecord*> connectionList;

    if ( connectionRecordsProtector.tryLock(defaultTimeout) ) {

        foreach ( ConnectionRecord * connection, connectionRecords )
            connectionList.append(connection);

        connectionRecordsProtector.unlock();

    }

    return connectionList;
}

void Server::sendAll( AbstractDatagramEncoder * datagram ) {

    // slow
    if ( connectionRecordsProtector.tryLock(defaultTimeout) ) {

        foreach ( ConnectionRecord * connection, connectionRecords )
            connection->send(datagram);

        connectionRecordsProtector.unlock();

    }

}

void Server::sendAll( const QList<AbstractDatagramEncoder*> & datagrams ) {

    // slow
    foreach ( AbstractDatagramEncoder * datagram, datagrams )
        sendAll(datagram);

}

void Server::stop() {

    sv_socket->close();

}

ConnectionRecord * Server::createConnectionRecord( QTcpSocket * socket ) {

    return new ConnectionRecord( socket, this );
}

void Server::on_serverSocket_newConnection() {

    // QyDbgLocation();
    ConnectionRecord * connection = createConnectionRecord( sv_socket->nextPendingConnection() );

    connect( connection->socket(), SIGNAL( disconnected() ), SLOT( disconnected() ) );
    QyNetDoProtected( connectionRecordsProtector, connectionRecords.append(connection), defaultTimeout );

    emit newConnection(connection);

}

void Server::disconnected() {

    // QyDbgLocation();
    ConnectionRecord * connection = 0;

    if ( QObject * socket = sender() )
        connection = qobject_cast<ConnectionRecord*>( socket->parent() );

    if (!connection) {

        qFatal("Received \"disconnected\" signal from not socket object or socket is not valid.");
        return;
    }

    QyNetDoProtected( connectionRecordsProtector, connectionRecords.removeAll(connection), defaultTimeout );

    emit connectionClosed(connection);
    connection->deleteLater();

}

void Server::init() {

    sv_socket = new QTcpServer(this);
    sv_socket->setObjectName("serverSocket");
    QMetaObject::connectSlotsByName(this);

}

/**************************************************************************************************/

Client::Client( QObject * parent ) : ConnectionRecord( new QTcpSocket, parent ) {
}

Client::Client( const QHostAddress & address, quint16 port, QObject * parent )
    : ConnectionRecord( new QTcpSocket, parent )
{

    connectToHost( address, port );

}

void Client::connectToHost( const QHostAddress & address, quint16 port ) {

    socket()->connectToHost( address, port );

}

void Client::disconnectFromHost() {

    Q_ASSERT( socket() );

    socket()->disconnectFromHost();

}
